package lt.inkredibl.iit;

import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.Rectangle2D.Double;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

public class Edge
{

	private List<Point2D> _points;

	private Edge(List<Point2D> points)
	{
		_points = points;
	}
	
	private Edge()
	{
		_points = new ArrayList<Point2D>();
	}
	
	public double length()
	{
		Point2D pp = null;
		double len = 0;
		for (Point2D pt: _points)
		{
			if (pp != null)
			{
				len += pp.distance(pt);
			}
			pp = pt;
		}
		return len;
	}
	
	/**
	 * Splits edge into specified number of intervals. Note, that the split
	 * returns exactly requested number of intervals i.e. is imprecise.
	 * @param count number of intervals
	 * @return count+1 points in list
	 */
	public Edge split(int count)
	{
		double len = length();
		List<Point2D> pts = new ArrayList<Point2D>();
		Iterator<Point2D> it = _points.iterator();
		Point2D pp = it.next();
		pts.add(pp);
		double intlen = len / count;
		double seglen = 0;
		Point2D pt = it.next();
		for (int i = 1; i < count; i++)
		{
			double dist = pp.distance(pt);
			while (dist + seglen < intlen * i)
			{
				pp = pt;
				pt = it.next();
				seglen += dist;
				dist = pp.distance(pt);
			}
			double d = dist + seglen - (intlen * i);
			double mul = d / dist;
			double x = pt.getX() - (mul * (pt.getX() - pp.getX()));
			double y = pt.getY() - (mul * (pt.getY() - pp.getY()));
			pts.add(new Point2D.Double(x, y));
		}
		pts.add(getLastPoint());
		return Edge.valueOf(pts);
	}
	
	public Edge stretch(Point2D p1, Point2D p2)
	{
		double p1x = p1.getX();
		double p1y = p1.getY();
		Point2D first = getFirstPoint();
		Point2D last = getLastPoint();
		double firstX = first.getX();
		double firstY = first.getY();
		double lenmul = p1.distance(p2) / first.distance(last);
		Edge e = new Edge();
		for (Point2D pt: _points)
		{
			e._points.add(new Point2D.Double(p1x + ((pt.getX() - firstX) * lenmul), p1y + ((pt.getY() - firstY) * lenmul)));
		}
		double targetAngle = angle(p1, p2);
		Point2D eLast = e._points.get(e._points.size()-1);
		double sourceAngle = angle(p1, eLast);
		double angle = targetAngle - sourceAngle;
		e.rotateInPlace(p1, angle);
		return e;
	}
	
	public static double angle(Point2D p1, Point2D p2)
	{
		double dist = p1.distance(p2);
		double xdist = p2.getX() - p1.getX();
		double ydist = p2.getY() - p1.getY();
		double asin = Math.asin(ydist / dist);// -pi/2 to pi/2
		if (xdist >= 0)
		{
			if (ydist >= 0)// I
			{
				return asin;
			}
			else// IV
			{
				return (2*Math.PI) + asin;
			}
		}
		else
		{
			if (ydist >= 0)// II
			{
				return Math.PI - asin;
			}
			else// III
			{
				return Math.PI - asin;
			}
		}
	}
	
	public void rotateInPlace(Point2D center, double angle)
	{
		double x = center.getX();
		double y = center.getY();
		double sin = Math.sin(angle);
		double cos = Math.cos(angle);
		for (Point2D pt: _points)
		{
			rotate(x, y, pt, pt, sin, cos);
		}
	}
	
	public static Point2D rotate(Point2D center, Point2D pt, double angle)
	{
		double cos = Math.cos(angle);
		double sin = Math.sin(angle);
		Point2D target = new Point2D.Double();
		double x = center.getX();
		double y = center.getY();
		rotate(x, y, pt, target, sin, cos);
		return target;
	}

	private static void rotate(double cx, double cy, Point2D source, Point2D target, double sin, double cos)
	{
		double sdx = (source.getX() - cx);
		double sdy = (source.getY() - cy);
		double dx = (sdx * cos) - (sdy * sin);
		double dy = (sdy * cos) + (sdx * sin);
		target.setLocation(cx+dx, cy+dy);
	}

	public static Edge valueOf(List<Point2D> edge)
	{
		return new Edge(edge);
	}

	public List<Point2D> getPoints()
	{
		return _points;
	}

	public double verticality()
	{
		Point2D first = getFirstPoint();
		Point2D last = getLastPoint();
		return Math.abs(first.getY() - last.getY()) / first.distance(last);
	}

	protected Point2D getLastPoint()
	{
		return _points.get(_points.size()-1);
	}

	protected Point2D getFirstPoint()
	{
		return _points.get(0);
	}

	public double approxX()
	{
		return (getFirstPoint().getX() + getLastPoint().getX()) / 2;
	}
	
	public double approxY()
	{
		return (getFirstPoint().getY() + getLastPoint().getY()) / 2;
	}
	
	public Double getBounds()
    {
		Point2D first = getFirstPoint();
		Rectangle2D.Double bounds = new Rectangle2D.Double(first.getX(), first.getY(), 0, 0);
		for (Point2D pt: _points)
		{
			bounds.add(pt);
		}
	    return bounds;
    }

	public Edge reverse()
	{
		ArrayList<Point2D> pts = new ArrayList<Point2D>(_points);
		Collections.reverse(pts);
		return valueOf(pts);
	}

	public static Edge valueOf(Point2D p1, Point2D p2)
	{
		return new Edge(Arrays.asList(new Point2D[] {p1, p2}));
	}

}
